#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Bitmap.h"

#include "Class.h"
#include "DevBitmap.h"
#include "Error.h"
#include "Storage.h"
#include "WindowSystem.h"
#include "String.h"
#include "OrdColl.h"
#include "FileData.h"
#include "ColorMapper.h"
#include "System.h"
#include "ET_stdio.h"

#include "HexFilter.h"
#include "LZWFilter.h"
#include "RLEFilter.h"
#include "A85Filter.h"

static ColorMapper *gMonoMapper;

static ColorMapper *GetMonoMapper()
{
	if (gMonoMapper == 0) {
		gMonoMapper= new MonoMapper();
		gMonoMapper->Ref();
	}
	return gMonoMapper;
}

ONEXIT(Bitmap)
{
	SafeUnref(gMonoMapper);
}

#define MAXCPP 3

struct XPMelem {
	char name[MAXCPP+1];
	u_long pixel;
};

char *Bitmap::ReadXPM(char *data[])
{
	register int ix, i, x, y;
	register char *cp, *buf;
	char wbuf[1000];
	int line= 0, tablen, s, len, cpp, depth;
	Point e;
	char *delim= " \t";
	XPMelem *tab;
	RGB rgb;

	if (data[line] == 0)
		return "short file";
	strcpy(wbuf, data[line++]);
	if (sscanf(wbuf, "%d %d %d %d", &e.x, &e.y, &len, &cpp) != 4)
		return "bad format";
		
	if (len <= 2)
		depth= 1;
	else if (len <= 4)
		depth= 2;
	else if (len <= 16)
		depth= 4;
	else if (len <= 256)
		depth= 8;
	else
		depth= 24;
		
	dbm= gWindowSystem->MakeDevBitmap(e, 0, depth);
	SafeRef(dbm);

	if (depth == 1)
		cmapper= GetMonoMapper();
	else if (depth <= 8)
		cmapper= new IndexMapper(1 << depth);
	else
		cmapper= new DirectMapper;
	SafeRef(cmapper);

	tablen= 128*MAXCPP;
	tab= new XPMelem[tablen];

	for (s= 0; s < len; s++) {
		char *c, *m, *cc;
		
		if ((buf= data[line++]) == 0)
			return "short color map";

		strcpy(wbuf, &buf[cpp]);
		cp= strtok(wbuf, delim);

		c= m= 0;
		while (cp) {
			char code[10];

			strcpy(code, cp);

			cp= strtok(0, delim);
			if (cp == 0)
				break;
		
			if (strcmp(code, "c") == 0)
				c= cp;
			else if (strcmp(code, "m") == 0)
				m= cp;
				
			cp= strtok(0, delim);
		}
		if (c == 0)
			c= m;
		if (m == 0)
			m= c;

		if (gColor)
			cc= c;
		else
			cc= m;

		if (cc == 0)
			continue;

		gInkManager->ParseColor(rgb, cc);

		ChangeRGB(s, &rgb);

		for (i= 0; i < tablen; i++) {
			ix= (*buf + i) % tablen;
			if (tab[ix].name[0] == 0) {
				strncpy(tab[ix].name, buf, cpp);
				tab[ix].pixel= s;
				break;
			}
		}
	}

	for (y= 0; y < e.y; y++) {
		if ((cp= data[line]) == 0)
			break;
		for (x= 0; x < e.x; x++) {
			if (cpp == 1)
				ix= *cp++;
			else {
				for (i= 0; i < tablen; i++) {
					ix= (*cp + i) % tablen;
					if (strncmp(tab[ix].name, cp, cpp) == 0)
						break;
				}
				if (i == tablen) {
					fprintf(stderr, "not found\n");
					break;
				}
				cp+= cpp;
			}
			SetPixel(x, y, tab[ix].pixel);
		}
		line++;
	}

	delete tab;
		
	return 0;
}

//---- SmartBitmap -------------------------------------------------------------

SmartBitmap::SmartBitmap(int w, int h, u_short *b) : size(w,h)
{
	u.bits= b;
	tag= 1;
}

SmartBitmap::SmartBitmap(const Point &s, u_short *b) : size(s)
{
	u.bits= b;
	tag= 1;
}

SmartBitmap::SmartBitmap(const char *nm)
{
	u.name= nm;
	tag= 2;
}

SmartBitmap::SmartBitmap(const char *xpm[])
{
	u.xpmdata= xpm;
	tag= 3;
}

SmartBitmap::SmartBitmap(char *xpm[])
{
	u.xpmdata= (const char**) xpm;
	tag= 3;
}

SmartBitmap::~SmartBitmap()
{
	if (tag == 0) {
		SafeDelete(u.bm);
	}
}

Bitmap *SmartBitmap::MakeBitmap()
{
	Bitmap *bm= 0;
	switch (tag) {
	case 1:
		if (u.bits)
			bm= new Bitmap(size, u.bits);
		break;
		
	case 2:
		if (u.name) {
			FileData *fd;
			if (u.name[0] == '/')
				fd= new FileData(u.name);
			else
				fd= new FileData(form("/%s/src/images/%s", gEtDir, u.name));
			bm= (Bitmap*) fd->AsObject(Meta(Bitmap));
			delete fd;
		}
		break;
		
	case 3:
		bm= new Bitmap(u.xpmdata);
		break;
		
	default:
		fprintf(stderr, "SmartBitmap::MakeBitmap(): oops\n");
		break;
	}
	tag= 0;
	return u.bm= bm;
}

//---- Bitmap ------------------------------------------------------------------

NewMetaImpl(Bitmap,Ink, (TP(dbm), TP(cmapper)));

Bitmap::Bitmap(const char *xpmdata[])
{
	char *msg= ReadXPM((char**)xpmdata);
	if (msg)
		fprintf(stderr, "ReadXPM: %s\n", msg);
}

Bitmap::Bitmap(const Point &sz, u_short depth, ColorMapper *cm)
{
	WindowSystem::WSInit();
	dbm= gWindowSystem->MakeDevBitmap(sz, 0, depth);
	SafeRef(dbm);
	cmapper= cm;
	SafeRef(cmapper);
}

Bitmap::Bitmap(const Point &sz, u_short depth)
{
	WindowSystem::WSInit();
	dbm= gWindowSystem->MakeDevBitmap(sz, 0, depth);
	SafeRef(dbm);

	if (depth == 1)
		cmapper= GetMonoMapper();
	else if (depth <= 8)
		cmapper= new IndexMapper(1 << depth);
	else
		cmapper= new DirectMapper;
	SafeRef(cmapper);
}

Bitmap::Bitmap(const Point &sz, u_short *im)
{
	WindowSystem::WSInit();
	dbm= gWindowSystem->MakeDevBitmap(sz, im, 1);
	SafeRef(dbm);
	cmapper= GetMonoMapper();
	SafeRef(cmapper);
}

Bitmap::Bitmap(int w, int h, u_short *im)
{
	WindowSystem::WSInit();
	dbm= gWindowSystem->MakeDevBitmap(Point(w, h), im, 1);
	SafeRef(dbm);
	cmapper= GetMonoMapper();
	SafeRef(cmapper);
}

Bitmap::Bitmap(DevBitmap *d)
{
	WindowSystem::WSInit();
	dbm= d;
	SafeRef(dbm);

	int depth= dbm->Depth();
	if (depth == 1)
		cmapper= GetMonoMapper();
	else if (depth <= 8)
		cmapper= new IndexMapper(1 << depth);
	else
		cmapper= new DirectMapper;
	SafeRef(cmapper);
}

Bitmap::Bitmap(const char*)
{
	WindowSystem::WSInit();
	fprintf(stderr, "Bitmap::Bitmap((char*): not yet implemented\n");
	dbm= 0;
}

Bitmap::~Bitmap()
{
	SafeUnref(dbm);
	SafeUnref(cmapper);
}

void Bitmap::CopyPixel(const Rectangle &t, Bitmap *from, const Point &fromorigin)
{
	Rectangle to(t);
	to.Clip(Size());
	to.extent= Min(to.extent, from->Size()-fromorigin);

	if (to.IsNotEmpty())
		dbm->DevBitBlt(to.origin.x, to.origin.y, to.extent.x, to.extent.y,
					eBitCopy, from->GetDevBitmap(), fromorigin.x, fromorigin.y);
}

void Bitmap::Fill(u_long pixel)
{
	if (dbm)
		dbm->DevBitBlt(0, 0, Size().x, Size().y, eBitSet, 0, 0, 0, pixel);
}

void Bitmap::FillRect(const Rectangle &r, u_long pixel)
{
	Rectangle t(r);
	t.Clip(Size());
	if (dbm)
		dbm->DevBitBlt(t.origin.x, t.origin.y, t.extent.x, t.extent.y, eBitSet,
															0, 0, 0, pixel);
}

Point Bitmap::Size()
{
	return dbm->Size();
}

int Bitmap::ShortsPerLine()
{
	return ((dbm->Size().x-1)/16+1)*dbm->Depth();
}

int Bitmap::BytesPerLine()
{
	return dbm->BytesPerLine();
}

int Bitmap::GetDepth()
{
	return dbm->Depth();
}

void Bitmap::SetDevBitmap(DevBitmap *d)
{
	AssignRef(&dbm, d);
}

void Bitmap::SetInk(Port *port)
{
	port->DevSetPattern(this);
}

void Bitmap::SetPixel(u_int x, u_int y, u_long val)
{
	dbm->SetPixel(x, y, val);
	dbm->changed= TRUE;
}

u_long Bitmap::GetPixel(u_int x, u_int y)
{
	return dbm->GetPixel(x, y);
}

void Bitmap::SetRGB(u_int x, u_int y, RGB *rgb)
{
	u_long pixel;
	if (cmapper->RGB2Pixel(rgb, &pixel))
		SetPixel(x, y, pixel);
}

void Bitmap::GetRGB(u_int x, u_int y, RGB *result)
{
	cmapper->Pixel2RGB(GetPixel(x, y), result);
}

u_long Bitmap::MaxPixelValue()
{
	return cmapper->MaxPixel();
}

void Bitmap::PixelToRGB(u_long pixel, RGB *result)
{
	cmapper->Pixel2RGB(pixel, result);
}

void Bitmap::ExpandDepth()
{
	register int x, y;
	DevBitmap *newdbm;
	ColorMapper *newcmapper;
	Point sz(Size());
	int depth= GetDepth();
	RGB c;

	if (depth == 1) {
		newdbm= gWindowSystem->MakeDevBitmap(sz, 0, 4);
		newcmapper= new IndexMapper(16);
	} else if (depth == 4) {
		newdbm= gWindowSystem->MakeDevBitmap(sz, 0, 8);
		newcmapper= new IndexMapper(256);
	} else {
		newdbm= gWindowSystem->MakeDevBitmap(sz, 0, 24);
		newcmapper= new DirectMapper;
	}

	for (y= 0; y < sz.y; y++) {
		for (x= 0; x < sz.x; x++) {
			u_long px= dbm->GetPixel(x, y);
			cmapper->Pixel2RGB(px, &c);
			newcmapper->RGB2Pixel(&c, &px);
			newdbm->SetPixel(x, y, px);
		}
	}
	dbm->Unref();
	dbm= newdbm;
	dbm->Ref();
	cmapper->Unref();
	cmapper= newcmapper;
	cmapper->Ref();
}

bool Bitmap::RGB2Pixel(RGB *rgb, u_long *pixel)
{
	if (!cmapper->RGB2Pixel(rgb, pixel)) {
		ExpandDepth();
		if (!cmapper->RGB2Pixel(rgb, pixel)) {
			fprintf(stderr, "cannot happen\n");
			return FALSE;
		}
	}
	dbm->changed= TRUE;
	return TRUE;
}

bool Bitmap::ChangeRGB(u_long pixel, RGB *rgb)
{
	if (!cmapper->ChangeRGB(pixel, rgb)) {
		ExpandDepth();
		if (!cmapper->ChangeRGB(pixel, rgb)) {
			fprintf(stderr, "cannot happen\n");
			return FALSE;
		}
	}
	dbm->changed= TRUE;
	return TRUE;
}

Object *Bitmap::deepclone()
{
	if (IsA() == Meta(Bitmap)) {
		Bitmap *bm= new Bitmap(Size(), GetDepth());

		SafeUnref(bm->dbm);
		
		if (dbm) {
			bm->dbm= dbm->MakeCopy();
			bm->dbm->Ref();
		}
		if (cmapper) {
			bm->cmapper= cmapper;
			bm->cmapper->Ref();
		}
		
		return bm;
	}
	return 0;
}

void Bitmap::Show(Rectangle*, Port*)
{
}

OStream &Bitmap::PrintOn(OStream &s)
{
	int version= 200, coding= 0;
	BitmapInfo bi(Size(), GetDepth(), 1);

	Object::PrintOn(s);

	if (bi.rowlen * Size().y > 100) {
		if (GetDepth() > 1)
			coding= 3;
		else
			coding= 2;
	}

	s << Size() SP << GetDepth() SP << version SP << coding SP << cmapper NL;

	if (coding == 0) {
		s.Push(new HexEncoder);
	} else if (coding == 1) {
		s.Push(new HexEncoder);
		s.Push(new RLEEncoder);
	} else if (coding == 2) {
		s.Push(new A85Encoder);
		s.Push(new RLEEncoder);
	} else if (coding == 3) {
		s.Push(new A85Encoder);
		s.Push(new LZWEncoder);
	}

	dbm->WriteData(&bi, s);

	if (coding > 0)
		s.Pop();

	s.Pop();
	return s;
}

IStream &Bitmap::ReadFrom(IStream &s)
{
	int coding, depth, version;
	Point size;

	SafeUnref(dbm);

	Object::ReadFrom(s);

	s >> size >> depth >> coding;

	dbm= gWindowSystem->MakeDevBitmap(size, 0, depth);
	SafeRef(dbm);

	if (coding < 200) {   // old version
		int cmsize;
		version= 0;
		
		if (depth == 1)
			cmapper= GetMonoMapper();
		else if (depth <= 8)
			cmapper= new IndexMapper(1 << depth);
		else
			cmapper= new DirectMapper;

		s >> cmsize;
		if (cmsize > 0) {
			s >> cmsize;
			if (cmsize > 0) {
				RGB c;
				for (int i= 0; i < cmsize; i++) {
					s >> c.red >> c.green >> c.blue;
					cmapper->ChangeRGB(i, &c);
				}
			}
		}
		if (coding >= 100) {
			version= 100;
			coding-= 100;
		}
	} else {            // new version
		version= coding;
		s >> coding >> cmapper;
	}
	SafeRef(cmapper);

	if (coding == 0) {
		s.Push(new HexDecoder);
	} else if (coding == 1) {
		s.Push(new HexDecoder);
		s.Push(new RLEDecoder(TRUE));
	} else if (coding == 2) {
		s.Push(new A85Decoder);
		s.Push(new RLEDecoder(FALSE));
	} else if (coding == 3) {
		s.Push(new A85Decoder);
		s.Push(new LZWDecoder);
	}

	BitmapInfo bi(Size(), depth, 1);

	dbm->ReadData(&bi, s);
	if (version == 100) {
		DevBitmap *mask= gWindowSystem->MakeDevBitmap(size, 0, 1);
		SafeRef(mask);
		
		BitmapInfo bi(Size(), 1, 1);
		mask->ReadData(&bi, s);
		mask->Unref();
	}

	if (coding > 0)
		s.Pop();

	s.Pop();
	return s;
}

